package cn.isjinhao.crws.sql.grammar.create;

import cn.isjinhao.crws.sql.grammar.ParseException;
import cn.isjinhao.crws.sql.grammar.AbstractParser;
import cn.isjinhao.crws.sql.grammar.CurdStatement;
import cn.isjinhao.crws.sql.grammar.AssignmentList;
import cn.isjinhao.crws.sql.grammar.Grouping;
import cn.isjinhao.crws.sql.grammar.create.represention.Insert;
import cn.isjinhao.crws.sql.grammar.create.represention.InsertSetRep;
import cn.isjinhao.crws.sql.grammar.create.represention.OnDuplicateKey;
import cn.isjinhao.crws.sql.grammar.create.represention.ValuesRep;
import cn.isjinhao.crws.sql.grammar.create.visitor.SingleInsertMetaInfoDetector;
import cn.isjinhao.crws.sql.lexical.scan.TokenSequence;
import cn.isjinhao.crws.sql.lexical.token.Token;
import cn.isjinhao.crws.sql.lexical.token.TokenType;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author ISJINHAO
 * @Date 2021/4/3 17:50
 */
public class InsertParser extends AbstractParser<CurdStatement> {

    public InsertParser(TokenSequence tokenSequence) {
        super(tokenSequence);
    }

    /**
     * insert           ->  "insert" (constrict)? "into" IDENTIFIER (valuesRep | setRep) (duplicateUpdate)?
     * constrict        ->  ignore
     *
     * valuesRep        ->  ("(" IDENTIFIER (, IDENTIFIER)* ")")? "values" ("(" binaryArithmetic (, binaryArithmetic)* ")")+
     * insertSetRep     ->  "set" entryList
     *
     * binaryArithmetic ->  unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     * unaryArithmetic  ->  ("!"|"-") unaryArithmetic | primary
     * primary          ->  "true" | "false" | "null" | NUMBER | STRING | "(" binaryArithmetic ")" | function
     * function         ->  functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
     * functionName     ->  "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
     * onDuplicateKey   ->  "on" "duplicate" "key" "update" entryList
     *
     * entryList        ->  (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
     */
    @Override
    public CurdStatement parse() {
        CurdStatement insert = insert();
        // SingleInsertMetaInfoDetector 在检测 Insert 语句的时候会加锁，这里每次都 new 对象的目的是为了提升并发度。
        new SingleInsertMetaInfoDetector().accept(insert);
        return insert;
    }

    private CurdStatement insert() {
        tokenSequence.consume(TokenType.INSERT, "insert语句需要以 'insert into' 开始");

        Token token = tokenSequence.takeCur();
        Token constrict = null;
        if(TokenType.IGNORE.equals(token.getType())) {
            constrict = token;
            tokenSequence.advance();
        }
        tokenSequence.consume(TokenType.INTO, "insert语句需要以 'insert into' 开始");

        Token tableName = tokenSequence.takeCur();
        tokenSequence.advance();

        CurdStatement curdStatement;
        if(tokenSequence.curEqual(TokenType.SET)) {
            curdStatement = setRep();
        } else {
            curdStatement = valuesRep();
        }

        if(!tokenSequence.checkExistsToken()) {
            return new Insert(constrict, tableName, curdStatement);
        }

        return new Insert(constrict, tableName, curdStatement, onDuplicateKey());
    }

    private CurdStatement onDuplicateKey() {
        tokenSequence.consume(TokenType.ON, "需要关键字 'on' 'duplicate' 'key' 'update'");
        tokenSequence.consume(TokenType.DUPLICATE, "需要关键字 'on' 'duplicate' 'key' 'update'");
        tokenSequence.consume(TokenType.KEY, "需要关键字 'on' 'duplicate' 'key' 'update'");
        tokenSequence.consume(TokenType.UPDATE, "需要关键字 'on' 'duplicate' 'key' 'update'");

        CurdStatement curdStatement = entryList();
        return new OnDuplicateKey(curdStatement);
    }

    /**
     * ("(" IDENTIFIER (, IDENTIFIER)* ")")? "values" ("(" binaryArithmetic (, binaryArithmetic)* ")") ("," "(" binaryArithmetic (, binaryArithmetic)* ")")*
     */
    private CurdStatement valuesRep() {
        List<Token> tokenList = null;
        if(tokenSequence.equalThenAdvance(TokenType.LEFT_PAREN)) {
            tokenList = new ArrayList<>();
            do {
                tokenSequence.consume(TokenType.IDENTIFIER, "当前 insert 语句的列名需要存在");
                tokenList.add(tokenSequence.takePre());
            } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
            tokenSequence.consume(TokenType.RIGHT_PAREN, "当前 insert 语句的列名需要使用 '()' 括起来");
        }
        tokenSequence.consume(TokenType.VALUES, "当前的 insert 语句需要 'values' 关键字");

        int currentCount = -1;
        if(tokenList != null) {
            currentCount = tokenList.size();
        }
        List<List<CurdStatement>> curdStatementsList = new ArrayList<>();
        // 处理多行的值
        do {
            List<CurdStatement> curdStatementList = new ArrayList<>();
            // 处理一行的值
            tokenSequence.consume(TokenType.LEFT_PAREN, "当前 insert 语句的值需要由 '()' 括起来");
            do {
                curdStatementList.add(binaryArithmetic());
            } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
            tokenSequence.consume(TokenType.RIGHT_PAREN, "当前 insert 语句的值需要由 '()' 括起来");
            if(curdStatementList.size() == currentCount || currentCount == -1) {
                currentCount = curdStatementList.size();
            } else {
                throw new ParseException("当前 insert 语句的列的数量和值的数量不一致", tokenSequence);
            }
            curdStatementsList.add(curdStatementList);
        } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
        if(curdStatementsList.isEmpty()) {
            throw new ParseException("insert 语句必须有值", tokenSequence);
        }
        return new ValuesRep(tokenList, curdStatementsList);
    }

    /**
     * "set" (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
     */
    private CurdStatement setRep() {
        tokenSequence.consume(TokenType.SET, "当前 insert 语句需要 'set' 关键字");
        CurdStatement curdStatement = entryList();
        if(!(curdStatement instanceof AssignmentList)) {
            throw new ParseException(" 'set' 后需要赋值语句");
        }
        AssignmentList assignmentList = (AssignmentList) curdStatement;
        if(assignmentList.getEntryList() == null || assignmentList.getEntryList().isEmpty()) {
            throw new ParseException("insert 语句必须有值", tokenSequence);
        }
        return new InsertSetRep(curdStatement);
    }

    /**
     * "true" | "false" | "null" | NUMBER | STRING | "(" binaryArithmetic ")" | function
     */
    @Override
    protected CurdStatement primary() {
        CurdStatement primary = super.primary();
        if(primary != null) {
            return primary;
        }

        // function
        Token token = tokenSequence.takeCur();
        if (checkFunctionName(token)) {
            return function();
        }

        // "(" binaryArithmetic ")"
        if (tokenSequence.equalThenAdvance(TokenType.LEFT_PAREN)) {
            CurdStatement curdStatement = binaryArithmetic();
            tokenSequence.consume(TokenType.RIGHT_PAREN, "Expect ')'");
            return new Grouping(curdStatement);
        }

        throw new ParseException("无法解析当前token: " + token, tokenSequence);
    }
}
